package com.quarkdown.core.pipeline

import com.quarkdown.core.context.Context
import com.quarkdown.core.context.MutableContext
import com.quarkdown.core.flavor.RendererFactory
import com.quarkdown.core.function.library.Library
import com.quarkdown.core.pipeline.error.PipelineException
import com.quarkdown.core.pipeline.output.OutputResource
import com.quarkdown.core.pipeline.stage.SharedPipelineData
import com.quarkdown.core.pipeline.stage.execute
import com.quarkdown.core.rendering.RenderingComponents

/**
 * A representation of the sequential set of actions to perform in order to produce an output artifact from a raw source.
 * Each component of the pipeline takes an input from the output of the previous one.
 * @param context initial context data shared across this pipeline, which will is filled with useful information
 *                that are handed over to other stages of this pipeline.
 *                This allows gathering information on-the-fly without additional visits of the whole tree
 * @param libraries libraries to load and look up functions from
 * @param renderer supplier of the renderer implementation to use, produced by the flavor's [RendererFactory]
 *                 with the output attributes of the parser as an argument
 * @param hooks optional actions to run after each stage has been completed
 * @see PipelineChainFactory for standard pipeline stage chains
 */
class Pipeline(
    private val context: MutableContext,
    val options: PipelineOptions,
    val libraries: Set<Library>,
    private val renderer: (RendererFactory, Context) -> RenderingComponents,
    val hooks: PipelineHooks? = null,
) {
    private val renderingComponents: RenderingComponents by lazy { renderer(context.flavor.rendererFactory, context) }

    /**
     * A read-only version of the context of this pipeline.
     */
    val readOnlyContext: Context
        get() = context

    fun copy(context: MutableContext = this.context): Pipeline =
        Pipeline(
            context = context,
            options = options,
            libraries = libraries,
            renderer = renderer,
            hooks = hooks,
        )

    /**
     * Executes the pipeline and calls the given [hooks] after each stage.
     * @param source the source code to process and execute the stages onto
     * @throws PipelineException if an uncaught error occurs
     * @return a set of output resources generated by the pipeline
     */
    fun executeUnwrapped(source: CharSequence): Set<OutputResource> {
        val chain = PipelineChainFactory.fullChain(source, this.renderingComponents, this.options)
        val sharedData =
            SharedPipelineData(
                pipeline = this,
                context = context,
            )
        return chain.execute(sharedData)
    }

    /**
     * Executes the pipeline and calls the given [hooks] after each stage.
     * @param source the source code to process and execute the stages onto
     * @throws PipelineException if an uncaught error occurs
     * @return a single output resource that wraps all the resources generated by the pipeline.
     */
    fun execute(source: CharSequence): OutputResource? {
        val resources = executeUnwrapped(source)

        // The output name of the final wrapped resource.
        val outputName = options.resourceName ?: context.documentInfo.name ?: "Untitled Quarkdown Document"

        return renderingComponents.postRenderer.wrapResources(outputName, resources)
    }
}
